package com.cq.hd.admin.filter;

import com.cq.hd.admin.shiro.JWTToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {

    /**
     * 如果带有 token，则对 token 进行检查，否则报没有权限访问，其他免JWTFilter严重的接口在ShiroConfig中配置
     * 这个方法如果返回false会再次调用一遍isLoginAttempt方法，要慎用，因为如果再调一遍isLoginAttempt方法，且返回true，则shiro会再调用一遍CustomRealm的doGetAuthenticationInfo方法
     * 详情请参考isLoginAttempt注释
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
        //判断请求的请求头是否带上 "Authorization"
        if (checkAuthorization(request, response)) {
//            判断是不是第二次进入，是则直接返回（Shiro支持Servlet3.0的异步请求）
            Boolean afterFiltered = (Boolean) (request.getAttribute("jwtShiroFilter.FILTERED"));
            if (afterFiltered != null && afterFiltered) {
                return true;
            }
            //如果存在，则进入 executeLogin 方法执行登入，检查 token 是否正确
            try {
                return executeLogin(request, response);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                //token 错误
                responseError(request, response, e.getMessage());
            }
        } else {
            HttpServletRequest req = (HttpServletRequest) request;
            String servletPath = req.getServletPath();
            if (servletPath == null) {
                //如果请求头不存在 Authorization，则直接报错（506，无操作权限，无权限访问）
                responseError(request, response, "您没有权限访问");
                return true;
            }
            if (servletPath.equals("/")) {
                return true;
            }
            if (!servletPath.startsWith("/")) {
                servletPath += "/";
            }
            if (servletPath.startsWith("/druid")) {
                return true;
            }
            //如果请求头不存在 Authorization，则直接报错（506，无操作权限，无权限访问）
            responseError(request, response, "您没有权限访问");
        }
        return false;
    }

    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Authorization 字段
     * <p>
     * 1)如果isAccessAllowed方法调用完之后返回false，则会再次调用一遍isLoginAttempt方法，如果没有重写isLoginAttempt方法则会调用父类的isLoginAttempt方法
     * 1.1)且如果在调用完isLoginAttempt方法之后返回true，则shiro会再调用一次CustomRealm的doGetAuthenticationInfo方法
     * 1.2)且如果在调用完isLoginAttempt方法之后返回false，则shiro不会再调用一次doGetAuthenticationInfo方法，此时要注意1.3描述
     * 1.3)如果isAccessAllowed方法在返回false之前没有写responseError方法重定向，且调用完isLoginAttempt方法之后返回false，则swagger调试时会一直弹框，需要输入用户密码，这种写法是有缺陷的，不提倡这样写
     * 2)如果isAccessAllowed方法调用完之后返回true，则不会再次调用一遍isLoginAttempt方法
     */
//    @Override
//    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
//        HttpServletRequest req = (HttpServletRequest) request;
//        String token = req.getHeader("Authorization");
//        return token != null && !"" .equals(token);
//    }
    //由isLoginAttempt改造而来，isAccessAllowed返回false，shiro会调用onAccessDenied，为避免上述1.1和1.2的情况，这边直接返回false
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {
        return false;
    }

    //由isLoginAttempt改造而来，方法自定义，不重写父类的isLoginAttempt方法
    protected boolean checkAuthorization(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        return token != null && !"".equals(token);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        log.error(e.getMessage(), e);
        //token 错误
        responseError(request, response, e.getMessage());
        return false;
    }

    /**
     * 检查token是否即将到期，如果是则更换token
     */
//    private void renewalToken(ServletRequest request, ServletResponse response) {
//        HttpServletResponse httpResponse = WebUtils.toHttp(response);
//        HttpServletRequest req = (HttpServletRequest) request;
//        String token = req.getHeader("Authorization");
//        //是否要更换token
//        if (JWTUtil.shouldRenewalToken(token)) {
//            httpResponse.setHeader("Authorization", JWTUtil.createToken(JWTUtil.getUserId(token)));
//        }
//    }

    /**
     * 执行登陆操作
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        JWTToken jwtToken = new JWTToken(token);
        try {
            // 提交给realm进行登入，如果错误他会抛出异常并被捕获
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功，返回true
            return true;
        } catch (AuthenticationException var5) {
            return this.onLoginFailure(jwtToken, var5, request, response);
        }
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) {
        //设置一个标记位（Shiro支持Servlet3.0的异步请求）
        request.setAttribute("jwtShiroFilter.FILTERED", true);
    }


    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletRequest request, ServletResponse response, String message) {
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码，否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            String redirect = httpServletRequest.getContextPath() + "/unauthorized/" + message;
            httpServletResponse.sendRedirect(redirect);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
